import { defineStore } from 'pinia'
import { ref } from 'vue'
import { appoxUtility } from '@/appox/appox'
import { AppoxGeneralSample } from '@/appox/appox'
import { isDark } from '@/utils/utility'

export const useBirdViewStore = defineStore('birdView', () => {
  // 状态
  const objectSensorChannel = ref(0)
  const laneSensorChannel = ref(0)
  const canvasRef = ref(null)
  const showHeading = ref(true)
  const showTrajectory = ref(false)
  const showContour = ref(false)
  const showFOV = ref(false)
  
  // 视图参数
  const vAxisLength = ref(100.0)
  const vAxisOffset = ref(30.0)
  const mouseDragLocOrigin = ref(0)
  const mouseDragValOrigin = ref(0)

  // 常量
  const MinVAxisLength = 6
  const MaxVAxisLength = 1100

  // 数据
  let vehicleSampleBuffer = []
  let objectSensorSampleBuffer = []
  let laneSensorSampleBuffer = []

  // 动作
  function setObjectSensorChannel(channel) {
    objectSensorChannel.value = channel
    objectSensorSampleBuffer = [] // 清空目标物缓冲区
  }

  function setLaneSensorChannel(channel) {
    laneSensorChannel.value = channel
    laneSensorSampleBuffer = [] // 清空车道线缓冲区
  }

  function setCanvasRef(ref) {
    canvasRef.value = ref
  }

  function onResetData() {
    vehicleSampleBuffer = []
    objectSensorSampleBuffer = []
    laneSensorSampleBuffer = []
  }

  function onInputData(data) {
    if (data instanceof AppoxGeneralSample) {
      if (vehicleSampleProtocols.includes(data.protocol)) {
        vehicleSampleBuffer.push(data)
        appoxUtility.clipSampleBuffer(vehicleSampleBuffer)
      } else if (objSensorSampleProtocols.includes(data.protocol) && data.channel == objectSensorChannel.value) {
        objectSensorSampleBuffer.push(data)
        appoxUtility.clipSampleBuffer(objectSensorSampleBuffer)
      } else if (laneSensorSampleProtocols.includes(data.protocol) && data.channel == laneSensorChannel.value) {
        laneSensorSampleBuffer.push(data)
        appoxUtility.clipSampleBuffer(laneSensorSampleBuffer)
      }
    }
  }

  // 鼠标交互相关方法
  function handleMouseDown(e) {
    if (e.buttons !== 1) return // 左键
    const rect = canvasRef.value.getBoundingClientRect()
    mouseDragLocOrigin.value = e.clientY - rect.height / 2
    mouseDragValOrigin.value = vAxisOffset.value
  }

  function handleMouseMove(e) {
    if (e.buttons !== 1) return // 左键
    const rect = canvasRef.value.getBoundingClientRect()
    const delta = e.clientY - rect.height / 2 - mouseDragLocOrigin.value
    vAxisOffset.value = mouseDragValOrigin.value + delta / rect.height * vAxisLength.value
    constrainVAxisOffset()
  }

  function handleMouseWheel(e) {
    const rect = canvasRef.value.getBoundingClientRect()
    const canvasY = e.clientY - rect.top
    const center = 0.5 - canvasY / rect.height
    if (e.deltaY < 0) {
      zoomIn(false, center)
    } else {
      zoomOut(false, center)
    }
  }

  function constrainVAxisOffset() {
    if (vAxisOffset.value < -200) vAxisOffset.value = -200
    if (vAxisOffset.value > 2000) vAxisOffset.value = 2000
  }

  function zoomIn(byClicked, center) {
    const vAxisLengthO = vAxisLength.value
    vAxisLength.value *= byClicked ? 0.666 : 0.97
    if (vAxisLength.value < MinVAxisLength) vAxisLength.value = MinVAxisLength
    vAxisOffset.value += center * (vAxisLengthO - vAxisLength.value)
    constrainVAxisOffset()
  }

  function zoomOut(byClicked, center) {
    const vAxisLengthO = vAxisLength.value
    vAxisLength.value *= byClicked ? 1.5 : 1.030927
    if (vAxisLength.value > MaxVAxisLength) vAxisLength.value = MaxVAxisLength
    vAxisOffset.value += center * (vAxisLengthO - vAxisLength.value)
    constrainVAxisOffset()
  }

  function updateCanvas() {
    let interest = appoxUtility.getInterestTimeLocal()
    if (interest == null) return

    let vehicleSample = appoxUtility.searchAndInterpolateSample(vehicleSampleBuffer, interest, vehicleSampleConverters, interpolateVehicleSample, 1.0)
    let objSensorSample = appoxUtility.searchAndInterpolateSample(objectSensorSampleBuffer, interest, objSensorSampleConverters, interpolateObjSensorSample, 1.0)
    let laneSensorSample = appoxUtility.searchAndInterpolateSample(laneSensorSampleBuffer, interest, laneSensorSampleConverters, interpolateLaneSensorSample, 1.0)

    const canvas = canvasRef.value
    if (!canvas) return

    // 调整 canvas 的实际尺寸以匹配其显示尺寸
    const rect = canvas.getBoundingClientRect()
    canvas.width = rect.width
    canvas.height = rect.height

    const ctx = canvas.getContext('2d')
    if (!ctx) return

    // 清空画布
    ctx.clearRect(0, 0, canvas.width, canvas.height)

    // 设置主要参数
    const paintColor = isDark ? '#FFFFFF' : '#000000'
    const assistColor = '#808080'
    const lineColor = isDark ? '#80FF80' : '#008000'
    const objectColor = isDark ? '#8080FF' : '#0000FF'

    // 坐标转换函数
    function pointToView(x, y) {
      const scale = 1.0 / vAxisLength.value
      const normX = -y * scale
      const normY = (vAxisLength.value * 0.5 + vAxisOffset.value - x) * scale
      return {
        x: normX * canvas.height + 0.5 * canvas.width,
        y: normY * canvas.height
      }
    }

    function valueToView(src) {
      const scale = 1.0 / vAxisLength.value
      const norm = src * scale
      return norm * canvas.height
    }

    // 绘制辅助线
    ctx.strokeStyle = assistColor
    ctx.lineWidth = 1
    ctx.beginPath()
    ctx.moveTo(pointToView(0, -20).x, pointToView(0, -20).y)
    ctx.lineTo(pointToView(0, 20).x, pointToView(0, 20).y)
    ctx.stroke()

    ctx.beginPath()
    ctx.moveTo(pointToView(vAxisOffset.value + vAxisLength.value * 0.5, 0).x, pointToView(vAxisOffset.value + vAxisLength.value * 0.5, 0).y)
    ctx.lineTo(pointToView(vAxisOffset.value - vAxisLength.value * 0.5, 0).x, pointToView(vAxisOffset.value - vAxisLength.value * 0.5, 0).y)
    ctx.stroke()

    // 绘制横线刻度
    for (let d = -20; d <= 20; d += 5) {
      if (d === 0) continue
      ctx.beginPath()
      ctx.moveTo(pointToView(-0.5, d).x, pointToView(-0.5, d).y)
      ctx.lineTo(pointToView(0.5, d).x, pointToView(0.5, d).y)
      ctx.stroke()

      if (canvas.height / vAxisLength.value > 8.0) {
        ctx.fillStyle = assistColor
        ctx.font = '10px Arial'
        const text = d + 'm'
        const textHeight = 10  // 字体大小
        ctx.fillText(text, pointToView(-0.5, d).x - textHeight/2, pointToView(-0.5, d).y + 10)
      }
    }

    // 绘制前向纵线刻度
    for (let d = 5; d < vAxisOffset.value + vAxisLength.value * 0.5; d += 5) {
      const markSize = d % 100 === 0 ? 20 : (d % 25 === 0 ? 5 : 1)
      ctx.beginPath()
      ctx.moveTo(pointToView(d, -markSize / 2).x, pointToView(d, -markSize / 2).y)
      ctx.lineTo(pointToView(d, markSize / 2).x, pointToView(d, markSize / 2).y)
      ctx.stroke()

      if (d % 100 === 0) {
        ctx.fillStyle = assistColor
        ctx.font = '14px Arial'
        const text = d + 'm'
        const textHeight = 14  // 字体大小
        ctx.fillText(text, pointToView(d, -markSize / 2).x + 2, pointToView(d, -markSize / 2).y + textHeight/2)
      } else if (d % 25 === 0 && canvas.height / vAxisLength.value > 2.0) {
        ctx.fillStyle = assistColor
        ctx.font = '12px Arial'
        const text = d + 'm'
        const textHeight = 12  // 字体大小
        ctx.fillText(text, pointToView(d, -markSize / 2).x + 2, pointToView(d, -markSize / 2).y + textHeight/2)
      } else if (canvas.height / vAxisLength.value > 8.0) {
        ctx.fillStyle = assistColor
        ctx.font = '10px Arial'
        const text = d + 'm'
        const textHeight = 10  // 字体大小
        ctx.fillText(text, pointToView(d, -markSize / 2).x + 2, pointToView(d, -markSize / 2).y + textHeight/2)
      }
    }

    // 绘制后向纵线刻度
    for (let d = -5; d > vAxisOffset.value - vAxisLength.value * 0.5; d -= 5) {
      const markSize = d % 100 === 0 ? 20 : (d % 25 === 0 ? 5 : 1)
      ctx.beginPath()
      ctx.moveTo(pointToView(d, -markSize / 2).x, pointToView(d, -markSize / 2).y)
      ctx.lineTo(pointToView(d, markSize / 2).x, pointToView(d, markSize / 2).y)
      ctx.stroke()

      if (d % 100 === 0) {
        ctx.fillStyle = assistColor
        ctx.font = '14px Arial'
        const text = d + 'm'
        const textHeight = 14  // 字体大小
        ctx.fillText(text, pointToView(d, -markSize / 2).x + 2, pointToView(d, -markSize / 2).y + textHeight/2)
      } else if (d % 25 === 0 && canvas.height / vAxisLength.value > 2.0) {
        ctx.fillStyle = assistColor
        ctx.font = '12px Arial'
        const text = d + 'm'
        const textHeight = 12  // 字体大小
        ctx.fillText(text, pointToView(d, -markSize / 2).x + 2, pointToView(d, -markSize / 2).y + textHeight/2)
      } else if (canvas.height / vAxisLength.value > 8.0) {
        ctx.fillStyle = assistColor
        ctx.font = '10px Arial'
        const text = d + 'm'
        const textHeight = 10  // 字体大小
        ctx.fillText(text, pointToView(d, -markSize / 2).x + 2, pointToView(d, -markSize / 2).y + textHeight/2)
      }
    }

    // 画本车driving tube
    let distance = 0
    if (vehicleSample) {
      distance = vehicleSample.kilometerAge * 1000
      if (showHeading.value) {
        const speed = vehicleSample.speed
        const curvature = vehicleSample.curvature
        const rearpos = vehicleSample.frontOverhang + vehicleSample.wheelBase

        // 计算左右边界模型参数
        const modelL = [1.75, 0, 0, 0]  // 左边界初始偏移1.75m
        const modelR = [-1.75, 0, 0, 0] // 右边界初始偏移-1.75m

        if (curvature != null) {
          modelL[1] = modelR[1] = rearpos * curvature
          if (Math.abs(curvature) > 0.001) {
            const radius = 1.0 / curvature
            const curvatureL = 1.0 / (radius - 1.75)
            const curvatureR = 1.0 / (radius + 1.75)
            modelL[2] = curvatureL / 2
            modelR[2] = curvatureR / 2
          } else {
            modelL[2] = modelR[2] = curvature / 2
          }
        }

        // 计算后轴中心点的y偏移
        const rearPosYL = -modelL[3] * rearpos * rearpos * rearpos + modelL[2] * rearpos * rearpos - modelL[1] * rearpos + modelL[0]
        const rearPosYR = -modelR[3] * rearpos * rearpos * rearpos + modelR[2] * rearpos * rearpos - modelR[1] * rearpos + modelR[0]
        const yOffset = (rearPosYL + rearPosYR) * 0.5
        modelL[0] -= yOffset
        modelR[0] -= yOffset

        // 计算绘制范围
        const rearBound = 0
        const frontBound = speed * 0.5
        const xBegin = rearBound - 10
        const xEnd = frontBound + 10
        const xBeginI = Math.floor(xBegin)
        const xEndI = Math.ceil(xEnd)
        const rowCount = xEndI - xBeginI + 1

        // 计算每个点的位置和透明度
        const pointsL = new Array(rowCount)
        const pointsR = new Array(rowCount)
        const alphas = new Array(rowCount)

        for (let i = xBeginI; i <= xEndI; i++) {
          const x = Math.min(Math.max(i, xBegin), xEnd)
          const yL = modelL[3] * x * x * x + modelL[2] * x * x + modelL[1] * x + modelL[0]
          const yR = modelR[3] * x * x * x + modelR[2] * x * x + modelR[1] * x + modelR[0]

          pointsL[i - xBeginI] = pointToView(x, yL)
          pointsR[i - xBeginI] = pointToView(x, yR)

          // 计算透明度
          let weight = 1.0
          if (x + 0.5 < rearBound) {
            weight = Math.max(0.0, 1.0 - (rearBound - x - 0.5) * 0.1)
          } else if (x + 0.5 > frontBound) {
            weight = Math.max(0.0, 1.0 - (x + 0.5 - frontBound) * 0.1)
          }
          alphas[i - xBeginI] = Math.floor(16.0 * (1.0 - Math.pow(1.0 - weight, 0.7)) * (Math.cos(0.16 * Math.PI * (distance + x) + 0.5 * Math.PI) + 3))
        }

        // 绘制driving tube
        for (let i = 0; i < rowCount - 1; i++) {
          const gradient = ctx.createLinearGradient(0, pointsL[i].y, 0, pointsL[i + 1].y)
          gradient.addColorStop(0, paintColor + alphas[i].toString(16).padStart(2, '0'))
          gradient.addColorStop(1, paintColor + alphas[i + 1].toString(16).padStart(2, '0'))

          ctx.fillStyle = gradient
          ctx.beginPath()
          ctx.moveTo(pointsL[i].x, pointsL[i].y)
          ctx.lineTo(pointsR[i].x, pointsR[i].y)
          ctx.lineTo(pointsR[i + 1].x, pointsR[i + 1].y)
          ctx.lineTo(pointsL[i + 1].x, pointsL[i + 1].y)
          ctx.closePath()
          ctx.fill()
        }
      }
    }

    // 绘制车道线
    if (laneSensorSample) {
      laneSensorSample.lines.forEach(line => {
        // 判断虚线类型
        const broken = [
          LineClass.Dashed,
          LineClass.DashedSolid,
          LineClass.DoubleDashed,
          LineClass.SolidDashed
        ].includes(line.classification)
        // 多项式参数
        const param = [line.param0, line.param1, line.param2, line.param3]
        const rearEnd = line.rearEnd == null ? -1000 : line.rearEnd
        const frontEnd = line.frontEnd == null ? 1000 : line.frontEnd
        const rearBound = laneSensorSample.rearBound
        const frontBound = laneSensorSample.frontBound
        const lineWidth = valueToView(line.width == null ? 0.15 : line.width)
        ctx.lineWidth = lineWidth

        const xBegin = Math.max(rearBound - 10, rearEnd)
        const xEnd = Math.min(frontBound + 10, frontEnd)
        const xBeginI = Math.floor(xBegin)
        const xEndI = Math.ceil(xEnd)

        if (broken) {
          for (let i = xBeginI; i < xEndI; i++) {
            const i1 = Math.max(i, xBegin)
            const i2 = Math.min(i + 1, xEnd)

            let w1 = (i + distance) * 0.08
            let w2 = (i + 1 + distance) * 0.08
            w1 -= Math.floor(w1)
            w2 -= Math.floor(w2)

            let i1s = 0, i2s = 0
            if (w1 < 0.5 && w2 < 0.5) {
              i1s = i2s = 0.5 + i
            } else if (w1 >= 0.5 && w2 >= 0.5) {
              i1s = i
              i2s = i + 1
            } else if (w1 >= 0.5 && w2 < 0.5) {
              i1s = i
              i2s = i + (1.0 - w1) / (w2 + 1.0 - w1)
            } else if (w1 < 0.5 && w2 >= 0.5) {
              i1s = i + (0.5 - w1) / (w2 - w1)
              i2s = i + 1
            }

            i1s = Math.max(i1s, xBegin)
            i2s = Math.min(i2s, xEnd)
            if (i2s < i1s) continue

            let weight = 1.0
            if (i + 0.5 < rearBound) weight = Math.max(0.0, 1.0 - (rearBound - i - 0.5) * 0.1)
            else if (i + 0.5 > frontBound) weight = Math.max(0.0, 1.0 - (i + 0.5 - frontBound) * 0.1)

            const alpha = 1.0 - Math.pow(1.0 - weight, 0.7)
            const fullAlpha = alpha > 0.99 ? 128 : Math.floor(alpha * 128)
            const virtualAlpha = alpha > 0.99 ? 32 : Math.floor(alpha * 32)

            const y1L = param[3] * i1 * i1 * i1 + param[2] * i1 * i1 + param[1] * i1 + param[0]
            const y2L = param[3] * i2 * i2 * i2 + param[2] * i2 * i2 + param[1] * i2 + param[0]
            const y1sL = param[3] * i1s * i1s * i1s + param[2] * i1s * i1s + param[1] * i1s + param[0]
            const y2sL = param[3] * i2s * i2s * i2s + param[2] * i2s * i2s + param[1] * i2s + param[0]

            if (i1s > i1) {
              ctx.strokeStyle = lineColor + virtualAlpha.toString(16).padStart(2, '0')
              ctx.beginPath()
              ctx.moveTo(pointToView(i1, y1L).x, pointToView(i1, y1L).y)
              ctx.lineTo(pointToView(i1s, y1sL).x, pointToView(i1s, y1sL).y)
              ctx.stroke()
            }
            if (i2s > i1s) {
              ctx.strokeStyle = lineColor + fullAlpha.toString(16).padStart(2, '0')
              ctx.beginPath()
              ctx.moveTo(pointToView(i1s, y1sL).x, pointToView(i1s, y1sL).y)
              ctx.lineTo(pointToView(i2s, y2sL).x, pointToView(i2s, y2sL).y)
              ctx.stroke()
            }
            if (i2 > i2s) {
              ctx.strokeStyle = lineColor + virtualAlpha.toString(16).padStart(2, '0')
              ctx.beginPath()
              ctx.moveTo(pointToView(i2s, y2sL).x, pointToView(i2s, y2sL).y)
              ctx.lineTo(pointToView(i2, y2L).x, pointToView(i2, y2L).y)
              ctx.stroke()
            }
          }
        } else {
          for (let i = xBeginI; i < xEndI; i++) {
            let weight = 1.0
            if (i + 0.5 < rearBound) weight = Math.max(0.0, 1.0 - (rearBound - i - 0.5) * 0.1)
            else if (i + 0.5 > frontBound) weight = Math.max(0.0, 1.0 - (i + 0.5 - frontBound) * 0.1)

            const alpha = 1.0 - Math.pow(1.0 - weight, 0.7)
            const fullAlpha = alpha > 0.99 ? 128 : Math.floor(alpha * 128)

            const i1 = Math.max(i, xBegin)
            const i2 = Math.min(i + 1, xEnd)

            const y1L = param[3] * i1 * i1 * i1 + param[2] * i1 * i1 + param[1] * i1 + param[0]
            const y2L = param[3] * i2 * i2 * i2 + param[2] * i2 * i2 + param[1] * i2 + param[0]

            ctx.strokeStyle = lineColor + fullAlpha.toString(16).padStart(2, '0')
            ctx.beginPath()
            ctx.moveTo(pointToView(i1, y1L).x, pointToView(i1, y1L).y)
            ctx.lineTo(pointToView(i2, y2L).x, pointToView(i2, y2L).y)
            ctx.stroke()
          }
        }
      })
    }

    // 画本车轨迹
    if (vehicleSample) {
      const vehicleLength = vehicleSample.vehicleLength

      if (showTrajectory.value && vehicleSample.trajectory) {
        ctx.strokeStyle = paintColor + '80'
        ctx.beginPath()
        for (let i = 0; i < vehicleSample.trajectory.length - 1; i++) {
          const src = vehicleSample.trajectory[i]
          const dst = vehicleSample.trajectory[i + 1]
          const alphaRatio = 1.0 - Math.pow(i / vehicleSample.trajectory.length, 0.7)
          ctx.strokeStyle = paintColor + Math.floor(alphaRatio * 128).toString(16).padStart(2, '0')
          ctx.beginPath()
          ctx.moveTo(pointToView(src.x - vehicleLength, src.y).x, pointToView(src.x - vehicleLength, src.y).y)
          ctx.lineTo(pointToView(dst.x - vehicleLength, dst.y).x, pointToView(dst.x - vehicleLength, dst.y).y)
          ctx.stroke()
        }
      }
    }

    // 绘制本车框
    const vehicleWidth = vehicleSample ? vehicleSample.vehicleWidth : 1.9
    const vehicleLength = vehicleSample ? vehicleSample.vehicleLength : 4.6

    // 绘制轮廓
    if (vehicleSample && vehicleSample.contour && vehicleSample.contour.length > 0) {
      // 使用contour数据绘制
      ctx.fillStyle = paintColor + '80' // 50%透明度
      ctx.beginPath()
      vehicleSample.contour.forEach((pt, i) => {
        if (i === 0) ctx.moveTo(pointToView(pt.x, pt.y).x, pointToView(pt.x, pt.y).y)
        else ctx.lineTo(pointToView(pt.x, pt.y).x, pointToView(pt.x, pt.y).y)
      })
      ctx.closePath()
      ctx.fill()
    } else {
      // 使用默认矩形框
      const a = { x: 0, y: -vehicleWidth / 2 }
      const b = { x: 0, y: vehicleWidth / 2 }
      const c = { x: -vehicleLength, y: vehicleWidth / 2 }
      const d = { x: -vehicleLength, y: -vehicleWidth / 2 }

      ctx.fillStyle = paintColor + '80' // 50%透明度
      ctx.beginPath()
      ctx.moveTo(pointToView(a.x, a.y).x, pointToView(a.x, a.y).y)
      ctx.lineTo(pointToView(b.x, b.y).x, pointToView(b.x, b.y).y)
      ctx.lineTo(pointToView(c.x, c.y).x, pointToView(c.x, c.y).y)
      ctx.lineTo(pointToView(d.x, d.y).x, pointToView(d.x, d.y).y)
      ctx.closePath()
      ctx.fill()
    }

    const triangleLength = Math.min(1.8, vehicleLength / 3)
    const e = { x: -0.1, y: 0 }
    const f = { x: -triangleLength, y: -vehicleWidth / 2 + 0.1 }
    const h = { x: -triangleLength, y: vehicleWidth / 2 - 0.1 }

    ctx.strokeStyle = paintColor
    ctx.beginPath()
    ctx.moveTo(pointToView(e.x, e.y).x, pointToView(e.x, e.y).y)
    ctx.lineTo(pointToView(f.x, f.y).x, pointToView(f.x, f.y).y)
    ctx.lineTo(pointToView(h.x, h.y).x, pointToView(h.x, h.y).y)
    ctx.closePath()
    ctx.stroke()

    // 绘制目标物
    if (objSensorSample) {
      objSensorSample.objects.forEach((obj, index) => {
        const confAlpha = obj.confidence == null ? 1.0 : (Math.max(0, Math.min(100, obj.confidence)) * 0.008 + 0.2)

        // 绘制轮廓
        if (showContour.value && obj.contour) {
          ctx.strokeStyle = objectColor + Math.floor(confAlpha * 255).toString(16).padStart(2, '0')
          ctx.beginPath()
          obj.contour.forEach((pt, i) => {
            if (i === 0) ctx.moveTo(pointToView(pt.x, pt.y).x, pointToView(pt.x, pt.y).y)
            else ctx.lineTo(pointToView(pt.x, pt.y).x, pointToView(pt.x, pt.y).y)
          })
          ctx.stroke()
        }

        // 绘制轨迹
        if (showTrajectory.value && obj.trajectory) {
          ctx.strokeStyle = objectColor + Math.floor(confAlpha * 128).toString(16).padStart(2, '0')
          ctx.beginPath()
          for (let i = 0; i < obj.trajectory.length - 1; i++) {
            const src = obj.trajectory[i]
            const dst = obj.trajectory[i + 1]
            const alphaRatio = 1.0 - Math.pow(i / obj.trajectory.length, 0.7)
            ctx.strokeStyle = objectColor + Math.floor(alphaRatio * confAlpha * 128).toString(16).padStart(2, '0')
            ctx.beginPath()
            ctx.moveTo(pointToView(src.x, src.y).x, pointToView(src.x, src.y).y)
            ctx.lineTo(pointToView(dst.x, dst.y).x, pointToView(dst.x, dst.y).y)
            ctx.stroke()
          }
        }

        // 绘制朝向
        if (showHeading.value && obj.speed() != null && obj.heading != null) {
          const count = Math.floor(obj.speed() * 0.3) + 2
          const angleCos = Math.cos(obj.heading * Math.PI / 180)
          const angleSin = Math.sin(obj.heading * Math.PI / 180)
          const halfCurv = obj.curvature == null ? 0 : obj.curvature * 0.5

          for (let i = 0; i < count; i++) {
            const ox1 = i
            const oy1 = halfCurv * ox1 * ox1
            const ox2 = i + 0.5
            const oy2 = halfCurv * ox2 * ox2

            const x1 = obj.posX + angleCos * ox1 - angleSin * oy1
            const y1 = obj.posY + angleSin * ox1 + angleCos * oy1
            const x2 = obj.posX + angleCos * ox2 - angleSin * oy2
            const y2 = obj.posY + angleSin * ox2 + angleCos * oy2

            const alphaRatio = 1.0 - Math.pow(Math.abs(i) / count, 0.7)
            ctx.strokeStyle = objectColor + Math.floor(alphaRatio * confAlpha * 128).toString(16).padStart(2, '0')
            ctx.beginPath()
            ctx.moveTo(pointToView(x1, y1).x, pointToView(x1, y1).y)
            ctx.lineTo(pointToView(x2, y2).x, pointToView(x2, y2).y)
            ctx.stroke()
          }
        }

        // 绘制目标物
        if (obj.posMode === PositionMode.ClosestPoint) {
          const center = pointToView(obj.cpx, obj.cpy)
          let size = 0
          if (obj.category() === ObjectClass.General) size = valueToView(1)
          else if (obj.category() === ObjectClass.Car) size = valueToView(1.9)
          else if (obj.category() === ObjectClass.Pedestrian) size = valueToView(0.5)
          else if (obj.category() === ObjectClass.Truck) size = valueToView(2.4)
          else if (obj.category() === ObjectClass.Bike) size = valueToView(0.9)
          else return

          ctx.fillStyle = objectColor + Math.floor(confAlpha * 128).toString(16).padStart(2, '0')
          ctx.beginPath()
          ctx.arc(center.x, center.y, size / 2, 0, Math.PI * 2)
          ctx.fill()

          // 绘制关键目标物标记
          if (objSensorSample.cipvIndex === index) {
            ctx.strokeStyle = objectColor + Math.floor(confAlpha * 255).toString(16).padStart(2, '0')
            ctx.beginPath()
            ctx.arc(center.x, center.y, size / 2, 0, Math.PI * 2)
            ctx.stroke()
          } else if (objSensorSample.lkoIndex === index) {
            ctx.strokeStyle = objectColor + Math.floor(confAlpha * 255).toString(16).padStart(2, '0')
            ctx.beginPath()
            ctx.arc(center.x, center.y, size / 2, Math.PI / 2, Math.PI * 3 / 2)
            ctx.stroke()
          } else if (objSensorSample.rkoIndex === index) {
            ctx.strokeStyle = objectColor + Math.floor(confAlpha * 255).toString(16).padStart(2, '0')
            ctx.beginPath()
            ctx.arc(center.x, center.y, size / 2, Math.PI * 3 / 2, Math.PI / 2)
            ctx.stroke()
          }
        } else if (obj.posMode === PositionMode.BoxCenter) {
          if (!obj.width || obj.width <= 0 || !obj.length || obj.length <= 0) return

          const angle = obj.heading ?? 0
          const center = new AppoxFloatPoint(obj.posX, obj.posY)
          const wid = obj.width
          const len = obj.length

          // 计算旋转后的顶点
          function rotate(x, y, centerX, centerY, angle) {
            const rad = angle * Math.PI / 180
            const cos = Math.cos(rad)
            const sin = Math.sin(rad)
            const dx = x - centerX
            const dy = y - centerY
            return {
              x: centerX + dx * cos - dy * sin,
              y: centerY + dy * cos + dx * sin
            }
          }

          const a = rotate(center.x + len / 2, center.y - wid / 2, center.x, center.y, angle)
          const b = rotate(center.x + len / 2, center.y + wid / 2, center.x, center.y, angle)
          const c = rotate(center.x - len / 2, center.y + wid / 2, center.x, center.y, angle)
          const d = rotate(center.x - len / 2, center.y - wid / 2, center.x, center.y, angle)

          ctx.fillStyle = objectColor + Math.floor(confAlpha * 128).toString(16).padStart(2, '0')
          ctx.beginPath()
          ctx.moveTo(pointToView(a.x, a.y).x, pointToView(a.x, a.y).y)
          ctx.lineTo(pointToView(b.x, b.y).x, pointToView(b.x, b.y).y)
          ctx.lineTo(pointToView(c.x, c.y).x, pointToView(c.x, c.y).y)
          ctx.lineTo(pointToView(d.x, d.y).x, pointToView(d.x, d.y).y)
          ctx.closePath()
          ctx.fill()

          // 绘制方向指示
          const triangleLength = Math.min(1.8, len / 3)
          // 使用原始坐标计算方向指示三角形的点
          const e = rotate(center.x + len / 2 - 0.1, center.y, center.x, center.y, angle)
          const f = rotate(center.x + len / 2 - triangleLength, center.y - wid / 2 + 0.1, center.x, center.y, angle)
          const h = rotate(center.x + len / 2 - triangleLength, center.y + wid / 2 - 0.1, center.x, center.y, angle)

          ctx.strokeStyle = objectColor + Math.floor(confAlpha * 255).toString(16).padStart(2, '0')
          ctx.beginPath()
          ctx.moveTo(pointToView(e.x, e.y).x, pointToView(e.x, e.y).y)
          ctx.lineTo(pointToView(f.x, f.y).x, pointToView(f.x, f.y).y)
          ctx.lineTo(pointToView(h.x, h.y).x, pointToView(h.x, h.y).y)
          ctx.closePath()
          ctx.stroke()

          // 绘制关键目标物标记
          if (objSensorSample.cipvIndex === index) {
            ctx.fillStyle = objectColor + Math.floor(confAlpha * 255).toString(16).padStart(2, '0')
            ctx.beginPath()
            ctx.moveTo(pointToView(e.x, e.y).x, pointToView(e.x, e.y).y)
            ctx.lineTo(pointToView(f.x, f.y).x, pointToView(f.x, f.y).y)
            ctx.lineTo(pointToView(h.x, h.y).x, pointToView(h.x, h.y).y)
            ctx.closePath()
            ctx.fill()
          } else if (objSensorSample.lkoIndex === index || objSensorSample.rkoIndex === index) {
            const mv = {
              x: (f.x + h.x) * 0.5,
              y: (f.y + h.y) * 0.5
            }
            ctx.fillStyle = objectColor + Math.floor(confAlpha * 255).toString(16).padStart(2, '0')
            ctx.beginPath()
            if (objSensorSample.rkoIndex === index) {
              ctx.moveTo(pointToView(e.x, e.y).x, pointToView(e.x, e.y).y)
              ctx.lineTo(pointToView(f.x, f.y).x, pointToView(f.x, f.y).y)
              ctx.lineTo(pointToView(mv.x, mv.y).x, pointToView(mv.x, mv.y).y)
            } else {
              ctx.moveTo(pointToView(e.x, e.y).x, pointToView(e.x, e.y).y)
              ctx.lineTo(pointToView(h.x, h.y).x, pointToView(h.x, h.y).y)
              ctx.lineTo(pointToView(mv.x, mv.y).x, pointToView(mv.x, mv.y).y)
            }
            ctx.closePath()
            ctx.fill()
          }
        }

        // 绘制位置标记
        const markerPos = pointToView(obj.posX, obj.posY)
        ctx.strokeStyle = paintColor + Math.floor(confAlpha * 255).toString(16).padStart(2, '0')
        ctx.beginPath()
        ctx.moveTo(markerPos.x - 4, markerPos.y)
        ctx.lineTo(markerPos.x + 4, markerPos.y)
        ctx.moveTo(markerPos.x, markerPos.y - 4)
        ctx.lineTo(markerPos.x, markerPos.y + 4)
        ctx.stroke()
      })

      // 绘制FOV
      if (showFOV.value) {
        objSensorSample.fovs.forEach(fov => {
          const center = pointToView(fov.positionX, fov.positionY)
          const radius = valueToView(fov.distanceRange)

          ctx.fillStyle = objectColor + '18' // 10%透明度
          if (fov.angleRange === 360) {
            ctx.beginPath()
            ctx.arc(center.x, center.y, radius, 0, Math.PI * 2)
            ctx.fill()
          } else {
            ctx.beginPath()
            ctx.arc(center.x, center.y, radius, 
              (-fov.orientation - 90 - fov.angleRange * 0.5) * Math.PI / 180,
              (-fov.orientation - 90 + fov.angleRange * 0.5) * Math.PI / 180)
            ctx.lineTo(center.x, center.y)
            ctx.closePath()
            ctx.fill()
          }
        })
      }
    }
  }

  return {
    // 状态
    objectSensorChannel,
    laneSensorChannel,
    canvasRef,
    showHeading,
    showTrajectory,
    showContour,
    showFOV,
    vAxisLength,
    vAxisOffset,
    // 动作
    setObjectSensorChannel,
    setLaneSensorChannel,
    setCanvasRef,
    onResetData,
    onInputData,
    updateCanvas,
    handleMouseDown,
    handleMouseMove,
    handleMouseWheel,
    zoomIn,
    zoomOut
  }
}) 